package k8s

import (
	"context"
	"fmt"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func extractFromPodTemplate(tpl corev1.PodTemplateSpec) *ResourceMetrics {
	res := NewEmptyMetrics()

	for _, c := range tpl.Spec.Containers {
		// CPU
		if re, ok := c.Resources.Requests[corev1.ResourceCPU]; ok {
			res.CPURequests.Add(re)
			res.HasCPU = true
		}
		if limit, ok := c.Resources.Limits[corev1.ResourceCPU]; ok {
			res.CPULimits.Add(limit)
			res.HasCPU = true
		}

		// Memory
		if re, ok := c.Resources.Requests[corev1.ResourceMemory]; ok {
			res.MemRequests.Add(re)
			res.HasMemory = true
		}
		if limit, ok := c.Resources.Limits[corev1.ResourceMemory]; ok {
			res.MemLimits.Add(limit)
			res.HasMemory = true
		}

		// GPU
		if re, ok := c.Resources.Requests["nvidia.com/gpu"]; ok {
			res.GPURequests.Add(re)
			res.HasGPU = true
		}
		if limit, ok := c.Resources.Limits["nvidia.com/gpu"]; ok {
			res.GPULimits.Add(limit)
			res.HasGPU = true
		}
	}

	return res
}

func StringSliceToMap(slice []string) map[string]struct{} {
	m := make(map[string]struct{})
	for _, s := range slice {
		m[s] = struct{}{}
	}
	return m
}

func ContainsUsingMap(m map[string]struct{}, str string) bool {
	_, ok := m[str]
	return ok
}

func (g *implK8s) GetDeploymentResourceMetrics(c context.Context, req *RequestWorkloads) (*ResponseResourceMetrics, error) {
	if err := req.Validate(); err != nil {
		return nil, err
	}
	depList, err := g.Clientset.AppsV1().Deployments(g.Namespace).List(c, metav1.ListOptions{})
	if err != nil {
		return nil, err
	}
	dest := StringSliceToMap(req.WorkloadNames)
	resp := NewEmptyResponseMetrics()
	for _, dep := range depList.Items {
		if ContainsUsingMap(dest, dep.Name) {
			res := extractFromPodTemplate(dep.Spec.Template)
			res.Namespace = g.Namespace
			res.WorkloadName = dep.Name
			res.WithGVK(&dep)
			resp.Add(res)
		}
	}
	return resp, nil
}

func (g *implK8s) GetStatefulSetResourceMetrics(c context.Context, req *RequestWorkloads) (*ResponseResourceMetrics, error) {
	if err := req.Validate(); err != nil {
		return nil, err
	}

	stsList, err := g.Clientset.AppsV1().StatefulSets(g.Namespace).List(c, metav1.ListOptions{})
	if err != nil {
		return nil, err
	}

	dest := StringSliceToMap(req.WorkloadNames)
	resp := NewEmptyResponseMetrics()

	for _, sts := range stsList.Items {
		if ContainsUsingMap(dest, sts.Name) {
			res := extractFromPodTemplate(sts.Spec.Template)
			res.Namespace = g.Namespace
			res.WorkloadName = sts.Name
			res.WithGVK(&sts)
			resp.Add(res)
		}
	}

	return resp, nil
}

func (g *implK8s) GetDaemonSetResourceMetrics(c context.Context, req *RequestWorkloads) (*ResponseResourceMetrics, error) {
	if err := req.Validate(); err != nil {
		return nil, err
	}

	dsList, err := g.Clientset.AppsV1().DaemonSets(g.Namespace).List(c, metav1.ListOptions{})
	if err != nil {
		return nil, err
	}

	dest := StringSliceToMap(req.WorkloadNames)
	resp := NewEmptyResponseMetrics()

	for _, ds := range dsList.Items {
		if ContainsUsingMap(dest, ds.Name) {
			res := extractFromPodTemplate(ds.Spec.Template)
			res.Namespace = g.Namespace
			res.WorkloadName = ds.Name
			res.WithGVK(&ds)
			resp.Add(res)
		}
	}

	return resp, nil
}

func (g *implK8s) GetJobResourceMetrics(c context.Context, req *RequestWorkloads) (*ResponseResourceMetrics, error) {
	if err := req.Validate(); err != nil {
		return nil, err
	}

	jobList, err := g.Clientset.BatchV1().Jobs(g.Namespace).List(c, metav1.ListOptions{})
	if err != nil {
		return nil, err
	}

	dest := StringSliceToMap(req.WorkloadNames)
	resp := NewEmptyResponseMetrics()

	for _, job := range jobList.Items {
		if ContainsUsingMap(dest, job.Name) {
			res := extractFromPodTemplate(job.Spec.Template)
			res.Namespace = g.Namespace
			res.WorkloadName = job.Name
			res.WithGVK(&job)
			resp.Add(res)
		}
	}

	return resp, nil
}

func (g *implK8s) GetCronJobResourceMetrics(c context.Context, req *RequestWorkloads) (*ResponseResourceMetrics, error) {
	if err := req.Validate(); err != nil {
		return nil, err
	}

	dest := StringSliceToMap(req.WorkloadNames)
	resp := NewEmptyResponseMetrics()

	// 优先 batch/v1
	cjV1List, errV1 := g.Clientset.BatchV1().CronJobs(g.Namespace).List(c, metav1.ListOptions{})
	if errV1 == nil {
		for _, cj := range cjV1List.Items {
			if ContainsUsingMap(dest, cj.Name) {
				tpl := cj.Spec.JobTemplate.Spec.Template
				res := extractFromPodTemplate(tpl)
				res.Namespace = g.Namespace
				res.WorkloadName = cj.Name
				res.WithGVK(&cj)
				resp.Add(res)
			}
		}
	}

	// 再尝试 batch/v1beta1（旧集群）
	cjBetaList, errBeta := g.Clientset.BatchV1beta1().CronJobs(g.Namespace).List(c, metav1.ListOptions{})
	if errBeta == nil {
		for _, cj := range cjBetaList.Items {
			if ContainsUsingMap(dest, cj.Name) {
				tpl := cj.Spec.JobTemplate.Spec.Template
				res := extractFromPodTemplate(tpl)
				res.Namespace = g.Namespace
				res.WorkloadName = cj.Name
				res.WithGVK(&cj)
				resp.Add(res)
			}
		}
	}

	// 两者都失败才是错误
	if errV1 != nil && errBeta != nil {
		return nil, fmt.Errorf("failed to list CronJobs: v1=%v, v1beta1=%v", errV1, errBeta)
	}

	return resp, nil
}
